-- BuildingBonusM
-- Created by chengb
-- 通用建筑奖励模块

module("BuildingBonusM", package.seeall);

-- 建筑查看配置
local buildingView = {};

-- 统计奖励
local fieldStat = {};
local MAX_BONUS_COUNT = 65535;

-- 内部函数声明

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    -- 检索建筑查看配置
    local building_view_CSV = get_building_view_CSV();
    for i = 1, #building_view_CSV do
        local class = building_view_CSV[i].class;
        buildingView[class] = building_view_CSV[i];
    end

    fieldStat = {};
    -- 转换下dungeon_appear_stat信息表
    local building_bonus_stat_CSV = get_building_bonus_stat_CSV();
    for i = 1, #building_bonus_stat_CSV do
        local row = building_bonus_stat_CSV[i];
        local field = row.field;

        fieldStat[field] = row;
    end
end

-- 初始化
function init()
    loadCsv();

    -- 关注获得物品的事件
    EventMgr.removeAll("BuildingBonusM");
    EventMgr.register("BuildingBonusM", event.BONUS_ITEM, function(args)
        whenDoItemBonus(args);
    end);
end

-- 检索建筑查看配置
function queryBuildingView(class, path)
    if not buildingView[class] then
        return nil;
    end

    if not path or path == "" then
        return buildingView[class];
    end

    return buildingView[class][path];
end

-- 获取建筑对话内容
function getBuildingDialog(gridData)
    local class = gridData.class;
    if not buildingView[class] then
        return "";
    end

    local dialog = buildingView[class]["dialog"];
    local prefix = "formula_";

    -- 如果配置了公式，则调用公式计算，否则直接取配置的值
    if type(dialog) == "string" and
       string.startWith(dialog, "formula_") then
       local formulaName = string.sub(dialog, string.len(prefix) + 1);
       return FormulaM.invoke(formulaName, gridData);
    else
       return dialog;
    end
end

-- 获取建筑奖励提示
function getBuildingBonusTip(class)
    if not buildingView[class] then
        return nil;
    end

    local tip = buildingView[class]["bonus_tip"];
    local prefix = "formula_";

    -- 如果配置了公式，则调用公式计算，否则直接取配置的值
    if type(tip) == "string" and
        string.startWith(tip, "formula_") then
        local formulaName = string.sub(tip, string.len(prefix) + 1);
        return FormulaM.invoke(formulaName);
    else
        return tip;
    end
end

-- 执行建筑奖励
function doBuildingBonus(bonus, desc)
    local round = CombatM.getRound();
    for key, info in pairs(bonus) do
        if key == "status" then
            local condition = table.deepcopy(info);
            local status = condition.status;

            -- 修正回合数
            if condition.end_round > 0 then
                condition.end_round = condition.end_round + round;
            end
            if condition.prop then
                condition["prop"][4] = condition.end_round;
            end

            condition.status = nil;
            CombatStatusM.applyStatus(ME.user, status, condition);
        elseif key == "prop" then
            if info[4] ~= -1 then
                info[4] = info[4] + round;
            end
            PropM.record(ME.user, info, "mixed");
        elseif key == "bonus" then
            if type(info[1]) == 'number' then
                -- 只有一种奖励
                BonusM.doBonus(info, desc);
            else
                -- 有多种奖励
                for _, bonus in pairs(info) do
                    BonusM.doBonus(bonus, desc);
                end
            end

        elseif key == "cost_per_attrib" then
            -- 扣除百分比属性
            for _, debuffInfo in pairs(info) do
                local value  = debuffInfo[3];
                local attrib = debuffInfo[2];
                local maxAttrib = "max_" .. attrib;
                local attribValue     = ME.user:queryAttrib(attrib);
                local maxAttribValue  = ME.user:queryAttrib(maxAttrib);
                local costValue  = math.modf(maxAttribValue * value / 1000);

                -- 扣除到省一点
                if costValue >= attribValue then
                    costValue = attribValue - 1;
                end

                costValue = iif(costValue >= 0, costValue, 0);

                if attrib == "hp" then        -- 播放伤害效果
                    EventMgr.fire(event.HERO_DAMAGED, { ["damage"] = costValue, });
                end
                ME.user:costAttrib(attrib, costValue);
            end
        elseif key == "dungeon_attrib" then
            -- 迷宫中属性
            if type(info[1]) == "string" then
                DungeonServiceM.addDungeonAttrib(info[1], info[2]);
            else
                for _, d_attrib in pairs(info) do
                    DungeonServiceM.addDungeonAttrib(d_attrib[1], d_attrib[2]);
                end
            end
        elseif key == "tv_learn_engineering" then
            -- 通过电视学习
            EngineeringManualM.rewardKnowledgePoint(info[1], info[2], {["from"] = "watch_tv"});
        elseif key == "damage" then
            -- 受创
            CombatM.doDamage(ME.user, ME.user, info);
        else
            -- TODO: 储物箱物品
        end
    end

    local log = string.format("%s建筑奖励%s", desc or "", tableToString(bonus));
    DungeonLogM.addLog(log);

    return true;
end

-- 执行建筑消耗
function doBuildingCost(eventCost, desc)
    local prefix = "formula_";
    if type(eventCost) == "string" and string.startWith(eventCost, prefix) then
        -- 配置了公式，先自行计算一遍
        local formulaName = string.sub(eventCost, string.len(prefix) + 1);
        eventCost = FormulaM.invoke(formulaName, ME.user);
    end

    if not eventCost or #eventCost <= 0 then
        return;
    end

    if type(eventCost[1]) == "number" then
        eventCost = { eventCost, };
    end

    for _, cost in ipairs(eventCost) do
        if cost[1] == 1 then
            ItemM.costAmount(ME.user, cost[2], cost[3])
        elseif cost[1] == 2 then
            ME.user:costAttrib(cost[2], cost[3]);
        end
    end
end

-- 计算残骸奖励
function calcWreckageBonus(pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 不是残骸
    if grid.type ~= GRID_TYPE_WRECKAGE then
        return {};
    end

    local configBonus = grid.bonus;
    local prefix = "formula_";
    if type(configBonus) == "string" and string.startWith(configBonus, prefix) then
        -- 配置了公式，先自行计算一遍
        local formulaName = string.sub(configBonus, string.len(prefix) + 1);
        configBonus = FormulaM.invoke(formulaName, ME.user);
    end

    -- 计算奖励
    local bonusList = FormulaM.invoke("FETCH_WRECKAGE_BONUS", ME.user, configBonus);

    -- 如果只有一种奖励，需要转换一下格式
    if type(bonusList[1]) == 'number' then
        bonusList = { bonusList };
    end

    return bonusList;
end

-- 触摸灯塔
function touch_lighthouse(pos)
    local grid = DungeonM.getGridByPos(pos);
    if GRID_TYPE_LIGHTHOUSE ~= grid.type or
        GRID_STATE_DISABLE == grid.state then

        return false;
    end

    local element = grid.element;
    local args = DungeonM.queryElement(element, "args");
    local stat_field = args["bonus_stat"];
    local click_times = args["click_times"] or 1;

    -- 领取过奖励了
    if getFieldStat(stat_field) > 0 or
        CombatStatM.getFieldStat(stat_field) >= click_times then

        return false;
    end

    -- 增加进度
    CombatStatM.addFieldStat(stat_field, 1);

    -- 如果达到奖励条件
    if CombatStatM.getFieldStat(stat_field) >= click_times then
        addFieldStat(stat_field, 1);

        grid:changeState(GRID_STATE_DISABLE);
        local bonus = args["lighthouse_bonus"];
        if not bonus then
            return false;
        end

        BonusM.doBonus(bonus, "lighthouse");

        local bonus_list = {};
        for i = 1, bonus[3] do
            table.insert(bonus_list, { bonus[1], bonus[2], 1, });
        end

        -- 抛出通用建筑奖励事件
        EventMgr.fire(event.COMM_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = grid.type,
            ["bonus"] = {["bonus"] = bonus_list, },
            ["remainGrid"] = true,
        });

        -- 抛出事件
        EventMgr.fire(event.LIGHTHOUSE_BONUS, pos);
    end

    return true;
end

-- 领取未来猫任务
function take_doraemon_mission(pos)
    -- 附一个属性就行了
    local grid = DungeonM.getGridByPos(pos);

    -- 不是未来猫
    if grid.type ~= GRID_TYPE_SLIME_EVENT or DungeonM.query(grid.class, "rule") ~= "doraemon" then
        return false;
    end

    -- 状态不对
    if grid.type == GRID_STATE_DISABLE then
        return false;
    end

    -- 如果已经领取过任务了
    local prop = PropM.combine(ME.user, "doraemon", "mission");
    if prop[3] > 0 then
        return false;
    end

    -- 领取任务（prop）
    local layer = DungeonM.currentLayer();

    prop = { PropM.getPropId("doraemon"), "mission", layer, -1, };
    PropM.record(ME.user, prop, "doraemon");

    -- 奖励
    local bonus = grid.bonus;
    if bonus then
        BonusM.doBonus(bonus, "doraemon");

        -- 抛出通用建筑奖励事件
        EventMgr.fire(event.COMM_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = grid.type,
            ["bonus"] = {["bonus"] = bonus, },
            ["remainGrid"] = true,
        });

    end

    return true;
end

-- 完成未来猫任务
function finish_doraemon_mission(pos, class_id)
    -- 附一个属性就行了
    local grid = DungeonM.getGridByPos(pos);

    -- 不是未来猫
    if grid.type ~= GRID_TYPE_SLIME_EVENT or DungeonM.query(grid.class, "rule") ~= "doraemon" then
        return false;
    end

    -- 状态不对
    if grid.type == GRID_STATE_DISABLE then
        return false;
    end

    -- 如果还没领取过任务
    local prop = PropM.combine(ME.user, "doraemon", "mission");
    if prop[3] <= 0 then
        return false;
    end

    -- 扣除道具
    if not ItemM.costAmount(ME.user, class_id, 1) then
        return false;
    end

    local dbase = DungeonM.queryElement(grid.element, "dbase") or {};
    if not dbase["pet_id"] then
        return false;
    end

    -- 状态不可用
    grid:changeState(GRID_STATE_DISABLE);

    local bonus = BonusM.doBonus({3, dbase["pet_id"], 1, });

    if bonus.id then
        bonus = { bonus.id, bonus.classId, bonus.count, };
    end

    EventMgr.fire(event.FINISH_DORAEMON, {["bonus"] = bonus, ["pos"] = pos });

    return true;
end

-- 领取隐藏物件-保险箱奖励
function takeHideStrongBox(pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 类型
    if grid.type ~= GRID_TYPE_HIDE_STRONGBOX or grid.state == GRID_STATE_DISABLE then
        return false;
    end

    local element = grid.element;
    local args = DungeonM.queryElement(element, "args") or {};
    local bonus_arg = args["bonus_arg"] or {};

    local bonus = grid.bonus;
    if not bonus then
        return false;
    end

    -- 钻石需要统计次数
    if "gem" == bonus[2] and bonus_arg["limit_rule"] then
        addFieldStat(bonus_arg["limit_rule"]);
    end

    doEvent(pos);
    -- BonusM.doBonus(bonus, "hide_strongbox");

    return true;
end

-- 领取残骸奖励
function takeWreckageBonus(pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 不是残骸
    if grid.type ~= GRID_TYPE_WRECKAGE then
        trace("BuildingBonusM", "格子%d不是残骸。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BuildingBonusM", "残骸失效了。");
        return false;
    end

    if grid.monster_die_limit == 1 and not DungeonM.isAllMonsterDead() then
        trace("BuildingBonusM", "尚有怪物未死亡，不能领取残骸奖励。")
        return false;
    end

    -- 计算奖励
    local bonusList = calcWreckageBonus(pos);

    for _, bonus in pairs(bonusList) do
        BonusM.doBonus(bonus, "wreckage");
    end

    -- 如果有需要记录字段
    local statKey = grid.bonus_stat;
    if statKey then
        local stat = DungeonM.getDungeonMixedField(statKey) or 0 + 1;
        DungeonM.setDungeonMixedField(statKey, stat);
    end

    -- 先把bonus数据删除，免得召唤到这个位置的怪物的奖励数据被覆盖掉
    grid.bonus = nil;

    -- 改变状态
    grid:changeState(GRID_STATE_DISABLE);

    -- 回合事件需要在功能做完之后再抛
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, pos);
    end

    -- 抛出事件
    EventMgr.fire(event.TAKE_WRECKAGE_BONUS, {["pos"] = grid:getPos(), ["bonus"] = bonusList,});

    -- 通知建筑事件完成
    EventMgr.fire(event.BUILDING_EVENT_DONE, {["pos"] = grid:getPos()});

    return true;
end

-- 埋葬水手残骸
function burySailorBonus(pos)
    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_SAILOR_WRECKAGE or grid.state == GRID_STATE_DISABLE then
        return false;
    end

    -- 改变状态
    grid:changeState(GRID_STATE_DISABLE);

    -- 幽灵船等级+1
    GhostShipM.levelUp();

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, pos);
    end

    -- 事件
    EventMgr.fire(event.BURY_SAILOR, pos);
    return true;
end

-- 提交祭品，只能提交两，超过两件就不能压缩到32位整数中了
function darkDragonSubmit(pos, classId1, classId2)
    local grid = DungeonM.getGridByPos(pos);

    -- 如果不是黑暗龙雕像
    if grid.type ~= GRID_TYPE_DARK_DRAGON then
        trace("BuildingBonusM", "不是黑暗龙雕像。");
        return false;
    end

    -- 状态不对
    if grid.state ~= GRID_STATE_OPEN then
        trace("BuildingBonusM", "黑暗龙雕像 %d 状态不允许提交。", pos);
        return false;
    end

    -- TODO: 不应该是相同的道具
    if not classId1 or not classId2 or classId1 == classId2 then
        return false;
    end

    -- 祭品、已经提交的祭品
    local sacrifice = grid.sacrifice;
    local submited = grid.submit or {};

    if table.indexOf(sacrifice, classId1) <= 0 or
        table.indexOf(sacrifice, classId2) <= 0 or
        submited[classId1] or submited[classId2] then
        trace("BuildingBonusM", "道具不能提交。");
        return false;
    end

    -- 先扣除道具
    if not ItemM.costAmount(ME.user, classId1, 1) or
        not ItemM.costAmount(ME.user, classId2, 1) then
        trace("BuildingBonusM", "道具不足，不能提交。");
        return false;
    end

    -- 标记雕像状态
    grid.state = GRID_STATE_DISABLE;

    -- 标记提交
    submited[classId1] = 1;
    submited[classId2] = 1;
    grid.submit = submited;

    DungeonLogM.addLog(string.format("黑暗龙雕像提交%d,%d", classId1, classId2));

    -- 抛出事件，奖励在slime_event中关注处理
    EventMgr.fire(event.DARK_DRAGON_SUBMIT, {["pos"] = pos, ["submit"] = submited, });

    -- 抛出通用建筑奖励事件 (为了隐藏建筑用)
    EventMgr.fire(event.COMM_BUILDING_BONUS, {
        ["pos"]   = pos,
        ["type"]  = grid.type,
        ["bonus"] = { ["bonus"] = {} },
    });
    return true;
end

-- 通用建筑事件处理
function doEvent(pos, remainGrid, isSync, notComEvent)
    local grid = DungeonM.getGridByPos(pos);
    local gridType = grid.type;

    assert(type(pos) == 'number');

    if not grid then
        trace("BuildingBonusM", "位置(%d)的格子不存在。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BuildingBonusM", "建筑格子(type = %d)已失效。", gridType);
        return false;
    end

    if grid.monster_die_limit == 1 and not DungeonM.isAllMonsterDead() then
        trace("BuildingBonusM", "尚有怪物未死亡，不能领取奖励。")
        return false;
    end

    local eventBonus = {};
    local eventCost  = {};
    local ret = {};
    local tips;

    local desc = "";
    if gridType == GRID_TYPE_JACK_CORPSE then
        -- 杰克的尸体
        eventBonus = FormulaM.invoke("CALC_JACK_CORPSE_BONUS", ME.user);
        desc = "jack_corpse";
    elseif gridType == GRID_TYPE_QUEEN_CORPSE then
        -- 皇后的尸体
        eventBonus = FormulaM.invoke("CALC_QUEEN_CORPSE_BONUS", ME.user, grid.bonus);
        desc = "queen_corpse";
    elseif gridType == GRID_TYPE_WOMAN_WRECKAGE then
        -- 女人的残骸
        eventBonus = FormulaM.invoke("CALC_WOMAN_WRECKAGE_BONUS", ME.user, grid.bonus);
        desc = "woman_wreckage";
    elseif gridType == GRID_TYPE_SKELETON then
        -- 骸骨堆
        eventBonus = FormulaM.invoke("CALC_SKELETON_BONUS", ME.user, grid.bonus);
        desc = "skeleton";
    elseif gridType == GRID_TYPE_HEAD_ALTAR then
        -- 头骨祭坛
        eventBonus = FormulaM.invoke("CALC_HEAD_ALTAR_BONUS", ME.user);
        desc = "head_altar";
    elseif gridType == GRID_TYPE_SHRINE then
        -- 神龛
        eventBonus = FormulaM.invoke("CALC_SHRINE_BONUS", ME.user);
        desc = "shrine";
    elseif gridType == GRID_TYPE_ZORO_CORPSE then
        -- 佐罗的尸体
        eventBonus = FormulaM.invoke("CALC_ZORO_CORPSE_BONUS", ME.user);
        desc = "zoro_corpse";
    elseif gridType == GRID_TYPE_PILGRIM then
        -- 朝圣者残骸
        eventBonus = FormulaM.invoke("CALC_PILGRIM_BONUS", ME.user);
        desc = "pilgrim";
    elseif gridType == GRID_TYPE_CAISSON then
        -- 弹药箱
        eventBonus = FormulaM.invoke("CALC_CAISSON_BONUS", ME.user, grid.bonus);
        desc = "caisson";
    elseif gridType == GRID_TYPE_SKUMMER then
        -- 走私者残骸
        eventBonus = FormulaM.invoke("CALC_SKUMMER_BONUS", ME.user, grid.bonus);
        desc = "skummer";
    elseif gridType == GRID_TYPE_CABIN then
        -- 魅魔小屋
        eventBonus = FormulaM.invoke("CALC_CABIN_BONUS", ME.user, grid.bonus);
        desc = "wreckage";
    elseif gridType == GRID_TYPE_ELEM_MINE then
        -- 元素矿脉
        eventBonus = FormulaM.invoke("CALC_ELEM_MINE_BONUS", ME.user, grid.elem_type);
        desc = "elem_mine";
    elseif gridType == GRID_TYPE_WINNER_STONE then
        -- 胜利者石柱
        eventBonus = FormulaM.invoke("CALC_WINNER_STONE_BONUS", ME.user, grid.bonus);
        desc = "winner_stone";
    elseif gridType == GRID_TYPE_SAILOR_WRECKAGE then
        -- 水手残骸
        eventBonus = FormulaM.invoke("CALC_SAILOR_WRECKAGE_BONUS", ME.user);
        desc = "sailor_wreckage";
    elseif gridType == GRID_TYPE_SHIPWRECK then
        -- 沉船
        eventBonus = FormulaM.invoke("CALC_SHIPWRECK_BONUS", ME.user);
        desc = "shipwreck";
    elseif gridType == GRID_TYPE_PIXY_WELL then
        -- 妖精之井
        eventBonus = FormulaM.invoke("CALC_PIXY_WELL_BONUS", ME.user);
        desc = "pixy_well";
    elseif gridType == GRID_TYPE_DESK then
        -- 木桌
        eventBonus = FormulaM.invoke("CALC_DESK_BONUS", ME.user);
        desc = "desk";
    elseif gridType == GRID_TYPE_STONE_BOX then
        -- 隐蔽的石匣
        eventBonus = FormulaM.invoke("CALC_STONE_BOX_BONUS", ME.user);
        desc = "stone_box";
    elseif gridType == GRID_TYPE_PIXY then
        -- 小妖精
        eventBonus = FormulaM.invoke("CALC_PIXY_BONUS", ME.user, grid.bonus);
        desc = "pixy";
    elseif gridType == GRID_TYPE_ODD_ALTAR then
        -- 奇怪的祭坛
        eventBonus = { ["bonus"] = grid.bonus, };
        desc = "odd_altar";
    elseif gridType == GRID_TYPE_ODD_FLOOR then
        eventBonus = FormulaM.invoke("CALC_ODD_FLOOR_BONUS", ME.user);
        desc = "odd_floor";
    elseif gridType == GRID_TYPE_MISTIC_CAVE then
        -- 神秘山洞
        eventBonus = FormulaM.invoke("CALC_MISTIC_CAVE_BONUS", ME.user);
        desc = "mistic_cave";
    elseif gridType == GRID_TYPE_CRYSTAL_BALL then
        -- 水晶球
        eventBonus = FormulaM.invoke("CALC_CRYSTAL_BALL_BONUS", ME.user);
        desc = "crystal_ball";
    elseif gridType == GRID_TYPE_STORAGE_BOX then
        -- 储物箱
        eventBonus = FormulaM.invoke("CALC_STORAGE_BOX_BONUS", ME.user, grid.bonus);
        desc = "storage_box";
    elseif gridType == GRID_TYPE_ARENA_BOX then
        -- 角斗台储物箱
        eventBonus = FormulaM.invoke("CALC_ARENA_BOX_BONUS", ME.user);
        desc = "arena_box";
    elseif gridType == GRID_TYPE_ACTIVITY_BOX then
        -- 活动副本储物箱
        eventBonus = { ["bonus"] = grid.bonus, };
        desc = "activity_box";
    elseif gridType == GRID_TYPE_AIOULIYA_STATUE then
        -- 艾欧利亚雕像
        eventBonus = FormulaM.invoke("CALC_AIOULIYA_STATUE_BONUS", ME.user);
        desc = "aiouliya_statue";
    elseif gridType == GRID_TYPE_OLD_STRONGBOX then
        -- 古老的保险箱
        eventBonus = FormulaM.invoke("CALC_OLD_STRONG_BONUS", ME.user);
        desc = "old_strongbox";
    elseif gridType == GRID_TYPE_SKY_OBJECT then
        -- 天空副本通用建筑
        eventBonus = FormulaM.invoke("CALC_SKY_INSTANCE_OBJECT_BONUS", ME.user, grid.bonusArg);
        desc = "sky_object";
    elseif gridType == GRID_TYPE_TRANSFORM_DEVICE then
        -- 改造装置
        eventBonus = FormulaM.invoke("CALC_TRANSFORM_DEVICE_BONUS", ME.user, grid.bonusArg);
        desc = "transform_device";
    elseif gridType == GRID_TYPE_DRAGON_SHADOW then
        -- 神龙虚影
        eventBonus = FormulaM.invoke("CALC_DRAGON_SHADOW_BONUS", ME.user);
        desc = "dragon_shadow";
    elseif gridType == GRID_TYPE_PALADIN_FOYER then
        -- 圣骑士休息室
        eventBonus = FormulaM.invoke("CALC_PALADIN_FOYER_BONUS", ME.user);
        desc = "paladin_foyer";
    elseif gridType == GRID_TYPE_INSECT_QUEEN then
        -- 虫族女王
        eventBonus = FormulaM.invoke("CALC_INSECT_QUEEN_BONUS", ME.user);
        desc = "insect_queen";
    elseif gridType == GRID_TYPE_JUSTICE then
        -- 正义使者
        eventBonus = FormulaM.invoke("CALC_JUSTICE_BONUS", ME.user);
        desc = "justice";
    elseif gridType == GRID_TYPE_KRYPTONITE then
        -- 氪石结界
        eventBonus = FormulaM.invoke("CALC_KRYPTONITE_BONUS", ME.user);
        desc = "kryptonite";
    elseif gridType == GRID_TYPE_NORMAL_OBJECT then
        -- 通用建筑对象
        eventBonus = calcNormalObjectBonus(ME.user, grid);
        desc = "normal_object";
    elseif gridType == GRID_TYPE_CAVE_HIDE_ITEM then
        -- 洞窟隐藏物件
        eventBonus = CaveHideItemM.calcHideItemBonus(grid);
        desc = "cave_hide_item";
    elseif gridType == GRID_TYPE_RECRUIT_PET then
        eventBonus = FormulaM.invoke("CALC_RECRUIT_PET_BONUS", grid.pet_id);
        desc = "recruit_pet";
    elseif gridType == GRID_TYPE_VITAMIN_CABIN then
        -- 维生舱
        eventBonus = FormulaM.invoke("CALC_VITAMIN_CABIN_BONUS", ME.user);
        desc = "vitamin_cabin";
    elseif gridType == GRID_TYPE_SPECIAL_HIDE_ITEM then
        eventBonus = CaveHideItemM.calcSpecialHideItemBonus(grid);
        desc = "special_hide_item"
    elseif gridType == GRID_TYPE_SKY_ELEM_MINE then
        eventBonus = FormulaM.invoke("CALC_SKY_ELEM_MINE_BONUS", ME.user, grid.elem_type);
        desc = "sky_elem_mine";
    elseif gridType == GRID_TYPE_COMMENT then
        -- Peter的木桌
        eventBonus = calcNormalObjectBonus(ME.user, grid);
        desc = "comment_bonus";
    elseif gridType == GRID_TYPE_MONSTER_BUILDING then
        -- 刷怪建筑
        eventBonus = FormulaM.invoke("CALC_MONSTER_BUILDING_BONUS", ME.user, grid.bonus, grid.class);
        desc = "monster_building";
    elseif gridType == GRID_TYPE_LOCKED_SLIME then
        -- 被关押的冈布奥
        eventBonus = {["bonus"] = {}};
        desc = "locked_slime";
    elseif gridType == GRID_TYPE_GODFATHER_STORAGE then
        -- 宇宙教父的储藏器
        eventBonus = {["bonus"] = grid.bonus};
        desc = "godfather_storage";

        -- 打一下标记
        addFieldStat("godfather_storage", 1);
    elseif gridType == GRID_TYPE_INTERFERE_DEVICE then
        -- 电流干扰装置
        eventBonus = FormulaM.invoke("CALC_INTERFERE_DEVICE_BONUS", ME.user, grid.bonus, grid.class);
        desc = "interfere_device";
    elseif gridType == GRID_TYPE_IRON_CHEST then
        -- 铁柜
        ret = FormulaM.invoke("CALC_IRON_CHEST_BONUS", ME.user, grid.bonus);
        eventBonus = { ["bonus"] = ret };
        desc = "iron_chest";
    elseif gridType == GRID_TYPE_CHAIR then
        -- 电椅
        eventBonus = FormulaM.invoke("CALC_CHAIR_BONUS", ME.user, grid.bonus);
        desc = "chair";
    elseif gridType == GRID_TYPE_TV then
        -- 电视机
        ret = FormulaM.invoke("CALC_TV_BONUS", ME.user);
        eventBonus = ret;
        -- 提示
        tips = ret["tip"];
        desc = "televation";
    elseif gridType == GRID_TYPE_GUARD_CHEST then
        -- 警卫室铁柜
        ret =  FormulaM.invoke("CALC_GUARD_CHEST_BONUS", ME.user);
        eventBonus = { ["bonus"] = ret };
        desc = "guard_chest";
    elseif gridType == GRID_TYPE_NORMAL_GAMBLE then
        -- 通用赌桌的奖励
        ret = DungeonGambleM.calcGridBonus(pos);
        eventBonus = { ["bonus"] = ret };
        desc = "normal_gamble";
    elseif gridType == GRID_TYPE_MASSAGE_ROOM then
        -- 按摩房
        ret = FormulaM.invoke("CALC_MASSAGE_ROOM_BONUS", ME.user, grid.class);
        eventBonus = { ["bonus"] = ret["bonus"], ["dungeon_attrib"] = ret["dungeon_attrib"] };
        tips = ret["tip"];
        desc = "massage_room";
    elseif gridType == GRID_TYPE_BONUS_BUILDING then
        -- 奖励建筑对象
        eventBonus = calcNormalObjectBonus(ME.user, grid);
        desc = "bonus_building";
    elseif grid.bonus then
        eventBonus = { ["bonus"] = grid.bonus, };
        desc = "default";
    else
        -- 暂不支持的类型
        assert(false, string.format("暂不支持格子类型为%d的建筑事件。", gridType));
    end

    assert(desc ~= "", "必须填写desc");

    -- 获取消耗
    local elementId = grid.element;
    local args = DungeonM.queryElement(elementId).args;
    eventCost = args.cost;

    -- 扣除消耗
    doBuildingCost(eventCost, desc);

    -- 执行奖励
    doBuildingBonus(eventBonus, desc);
    grid.bonus = nil;

    -- 如果抽到洞窟特殊奖励。记录下次数(可能需要记录下次数)
    if gridType == GRID_TYPE_SPECIAL_HIDE_ITEM or gridType == GRID_TYPE_CAVE_HIDE_ITEM then
        CaveHideItemM.addCaveBuildingBonusTimes(grid.class, eventBonus);
    end

    -- 如果有需要记录字段
    local statKey = grid.activity_bonus_stat;
    if statKey then
        addFieldStat(statKey, 1);
    end

    local awakeAppearLimit = grid.awake_appear_limit;
    if type(awakeAppearLimit) == "number" then
        BuffDataM.set("awake_dungeon_appear_limit", awakeAppearLimit, 1);
    end

    -- 改变状态
    if gridType ~= GRID_TYPE_COMMENT then
        -- peter的木桌要保留
        grid:changeState(GRID_STATE_DISABLE);
    end

    local costRound = DungeonM.isCostRound(grid);
    if gridType == GRID_TYPE_LOCKED_SLIME then
        -- 被关押的冈布奥

        -- 召唤 丧心病狂的冈布奥
        DungeonM.transGridToMonster(pos, grid.summon_element);
    end

    -- 默认需要添加同步缓存
    isSync = iif(isSync == nil, true, false);

    -- 回合事件
    if costRound then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos, ["bonus"] = eventBonus["bonus"], ["cost"] = eventCost, });
    end

    -- 是否保留格子
    remainGrid = remainGrid or false;

    if notComEvent then
        -- 奖励事件，格子自己处理奖励
        EventMgr.fire(event.GRID_SHOW_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = gridType,
            ["bonus"] = eventBonus,
        });
    else
        -- 抛出通用建筑奖励事件
        EventMgr.fire(event.COMM_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = gridType,
            ["bonus"] = eventBonus,
            ["remainGrid"] = remainGrid,
            ["cost"] = eventCost,
            ["tips"] = tips,
            ["desc"] = desc,
        });
    end

    -- 通知建筑事件完成
    EventMgr.fire(event.BUILDING_EVENT_DONE, {["pos"] = pos,});

    return true, not isSync;
end

-- 计算特殊建筑奖励
function calcSpecialBuildingBonus(pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 不是特殊建筑
    if grid.type ~= GRID_TYPE_SPECIAL_BUILDING then
        return {};
    end

    local configBonus = grid.bonus;

    local prefix = "formula_";
    if type(configBonus) == "string" and string.startWith(configBonus, prefix) then
        -- 配置了公式，先自行计算一遍
        local formulaName = string.sub(configBonus, string.len(prefix) + 1);
        configBonus = FormulaM.invoke(formulaName, ME.user, grid.bonusArg);
    end

    local bonusList = configBonus or {};

    -- 如果只有一种奖励，需要转换一下格式
    if type(bonusList[1]) == 'number' then
        bonusList = { bonusList };
    end

    return bonusList;
end

-- 领取建筑奖励
function takeSpecialBuildingBonus(pos)
    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_SPECIAL_BUILDING then
        trace("BuildingBonusM", "格子%d不匹配。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BuildingBonusM", "格子失效了。");
        return false;
    end

    local dbase = DungeonM.queryElement(grid.element, "dbase");
    if type(dbase) == "table" then
        if type(dbase["need_property"]) == "number" then
            -- 配置了需要道具才领取
            if ItemM.getAmount(ME.user, dbase["need_property"]) <= 0 then
                doAlert(getLocStr("dont_have_key"));
                return false;
            end
        elseif type(dbase["cost_item"]) == "table" then
            local costItem = dbase["cost_item"];
            local buildingInfo = BuildingBonusM.queryBuildingView(grid.class) or {};
            if ItemM.getAmount(ME.user, costItem[2]) < costItem[3] then
                doAlert(buildingInfo["fail_tip"]);
                return false;
            else
                doBuildingCost(costItem, "special_building");
            end
        end
    end

    -- 计算奖励
    local bonusList = calcSpecialBuildingBonus(pos);

    for _, bonus in pairs(bonusList) do
        BonusM.doBonus(bonus, "special_building");
    end

    -- 先把bonus数据删除，免得召唤到这个位置的怪物的奖励数据被覆盖掉
    grid.bonus = nil;

    -- 改变状态
    grid:changeState(GRID_STATE_DISABLE);

    -- 回合事件需要在功能做完之后再抛
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, pos);
    end

    -- 抛出事件
      -- 抛出通用建筑奖励事件
    EventMgr.fire(event.COMM_BUILDING_BONUS, {
        ["pos"]   = pos,
        ["type"]  = grid.type,
        ["bonus"] = {["bonus"] = bonusList},
        ["remainGrid"] = false,
        ["cost"] = nil,
    });

    -- 通知建筑事件完成
    EventMgr.fire(event.BUILDING_EVENT_DONE, {["pos"] = grid:getPos()});

    return true;
end

-- 统计
function updateFieldStat(lpc_stat)
    if not lpc_stat then
        return;
    end

    local buffer = Buffer.create(lpc_stat);
    local offset = 1;
    local size = Buffer.size(buffer);
    local stat = ME.user.dbase:query("buildingBonusStat", {});

    while (offset <= size) do
        local count = Buffer.getu16(buffer, offset);
        if (count >= 0) then
            local id = math.modf(offset / 2);
            id = id + 1;

            stat[id] = count;
        end

        -- 继续下一个记录
        offset = offset + 2;
    end

    ME.user.dbase:set("buildingBonusStat", stat);
end

-- 增加统计
function addFieldStat(field, add)
    if not fieldStat[field] then
        return 0;
    end

    add = add or 1;

    local id = fieldStat[field]["id"];
    if not id then
        return 0;
    end

    local stat = ME.user.dbase:query("buildingBonusStat", {});
    local count = (stat[id] or 0) + add;

    -- 不能超过上限
    count = math.min(count, MAX_BONUS_COUNT);

    stat[id] = count;

    ME.user.dbase:set("buildingBonusStat", stat);

    return count;
end

-- 设置统计
function setFieldStat(field, count)
    if not fieldStat[field] then
        return 0;
    end

    local id = fieldStat[field]["id"];
    if not id then
        return 0;
    end

    local stat = ME.user.dbase:query("buildingBonusStat", {});
    stat[id] = count;

    ME.user.dbase:set("buildingBonusStat", stat);

    return count;
end

-- 获取统计
function getFieldStat(field)
    -- 如果没有配置
    if not fieldStat[field] then
        return 0;
    end

    local id = fieldStat[field]["id"];
    if not id then
        return 0;
    end

    local stat = ME.user.dbase:query("buildingBonusStat", {});

    return stat[id] or 0;
end

-- 计算通用建筑奖励
function calcNormalObjectBonus(user, grid)
    local configBonus = grid.bonus;
    local prefix = "formula_";
    local bonusList = {};

    if type(configBonus) == "table" then
        bonusList = configBonus;
    elseif type(configBonus) == "string" and string.startWith(configBonus, prefix) then
        -- 配置了公式，调用公式计算
        local formulaName = string.sub(configBonus, string.len(prefix) + 1);
        bonusList = FormulaM.invoke(formulaName, ME.user);
    end

    assert(type(bonusList) == "table", "奖励必须为table类型");

    -- 如果只有一种奖励，需要转换一下格式
    if type(bonusList[1]) == 'number' then
        bonusList = { bonusList };
    end

    return {["bonus"] = bonusList,};
end

-- 兑换获得奖励
function exchangeBonus(pos)
    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_EXCHANGER then
        trace("BuildingBonusM", "格子%d不匹配。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("BuildingBonusM", "格子失效了。");
        return false;
    end

    local dbase = DungeonM.queryElement(grid.element, "dbase");
    local classId = dbase["submit_item"];
    local bonus = grid.bonus;

    assert(type(classId) == 'number', "必须配置提交的物品ID");
    assert(type(bonus) == 'table', "必须配置提交后获得的奖励");

    if ItemM.getAmount(ME.user, classId) <= 0 then
        trace("BuildingBonusM", "道具(%d)数量不足。", classId);
        return false;
    end

    ItemM.costAmount(ME.user, classId, 1);

    BonusM.doBonus(bonus, "exchange_bonus");

    EventMgr.fire(event.EXCHANGE_BONUS, { ["pos"] = pos, ["bonus"] = bonus, });

    return true;
end

-- 获得物品的事件回调
function whenDoItemBonus(args)
    local classId = args["classId"];
    local amount  = args["amount"];

    if amount <= 0 then
        return;
    end

    local dbase = PropertyM.query(classId, "dbase") or {};
    if type(dbase) == "table" and dbase["own_stat"] then
        addFieldStat(dbase["own_stat"], amount);
    end
end
